Imports System.IO
Imports System.Xml
Imports System.Reflection

Public Class BBXProject

    Public Enum ProjectType
        SQL
        SCRIPT
    End Enum

    Public Enum ProjectLang
        VBNET11
        CSNET11
        VBNET20
        CSNET20
    End Enum

    Public Enum errState
        OK
        INVALID_FILETYPE
        IO_ERROR
    End Enum

    Private Class Versions
        Public Const Revision3 As String = "BizBloxR3 Project File Definition"
        Public Const Revision4 As String = "BizBloxR4 Project File Definition"
        Public Const Revision5 As String = "BizBloxR5 Project File Definition"

        Public Shared Function CurrentVersion() As String
            Return Versions.Revision5
        End Function
    End Class

    Private _projType As ProjectType = ProjectType.SQL
    Private _src As String
    Private _trg As String
    Private _errState As errState
    Private _errMsg As String
    Private _outputDObj As Boolean = False
    Private _outputDCol As Boolean = False
    Private _outputTObj As Boolean = False
    Private _outputLang As ProjectLang = ProjectLang.VBNET11
    Private _enableDTStyleDateMapping As Boolean = True
    Private _enableRuntimeBoundsCheck As Boolean = True
    Private _mappedTables As New Hashtable

    Public Enum NodeName
        Version
        Project
        Configuration
        Mapping
        Table
        InheritsFrom
        ParentTable
        ParentColumn
        ChildColumn
        GenerateDefaultConstructor
    End Enum

    Private Enum ConfigName
        Source
        Target
        ProjectType
        OutputDataObject
        OutputDOCollection
        OutputDOTO
        OutputLang
        EnableBoundsChecking
        EnableDTDateMapping
    End Enum

    Public ReadOnly Property ErrorMessage() As String
        Get
            Return _errMsg
        End Get
    End Property

    Public ReadOnly Property ErrorState() As errState
        Get
            Return _errState
        End Get
    End Property


#Region "Configuration and Mapping Values"

    Public Property Source() As String
        Get
            Return _src
        End Get
        Set(ByVal Value As String)
            _src = Value
        End Set
    End Property

    Public Property Destination() As String
        Get
            Return _trg
        End Get
        Set(ByVal Value As String)
            _trg = Value
        End Set
    End Property

    Public Property CurrentProject() As ProjectType
        Get
            Return _projType
        End Get
        Set(ByVal Value As ProjectType)
            _projType = Value
        End Set
    End Property

    Public Property OutputDOTO() As Boolean
        Get
            Return Me._outputTObj
        End Get
        Set(ByVal Value As Boolean)
            Me._outputTObj = Value
        End Set
    End Property

    Public Property OutputDataObject() As Boolean
        Get
            Return Me._outputDObj
        End Get
        Set(ByVal Value As Boolean)
            Me._outputDObj = Value
        End Set
    End Property

    Public Property OutputDOCollection() As Boolean
        Get
            Return Me._outputDCol
        End Get
        Set(ByVal Value As Boolean)
            Me._outputDCol = Value
        End Set
    End Property

    Public Property OutputLanguage() As ProjectLang
        Get
            Return Me._outputLang
        End Get
        Set(ByVal Value As ProjectLang)
            Me._outputLang = Value
        End Set
    End Property

    Public Property EnableDTStyleDateMapping() As Boolean
        Get
            Return Me._enableDTStyleDateMapping
        End Get
        Set(ByVal value As Boolean)
            Me._enableDTStyleDateMapping = value
        End Set
    End Property

    Public Property EnableRuntimeBoundsCheck() As Boolean
        Get
            Return Me._enableRuntimeBoundsCheck
        End Get
        Set(ByVal value As Boolean)
            Me._enableRuntimeBoundsCheck = value
        End Set
    End Property

    Public Property MappedTables() As Hashtable
        Get
            Return Me._mappedTables
        End Get
        Set(ByVal value As Hashtable)
            Me._mappedTables = value
        End Set
    End Property

#End Region


#Region "Loading"

    Public Sub Load(ByVal fileName As String)
        Dim sr As StreamReader = Nothing
        Try
            Dim peekStream As Stream = File.OpenRead(fileName)
            sr = New StreamReader(peekStream)
            Dim line As String = sr.ReadLine()
            sr.Close()
            If line = BBXProject.Versions.Revision3 OrElse line = BBXProject.Versions.Revision4 Then
                Me.LoadTextFile(fileName)
            Else
                Me.LoadXMLFile(fileName)
            End If
            If Not Me.Source Is Nothing Then
                Me.Import()
            End If
        Catch ex As Exception
            If Not sr Is Nothing Then
                sr.Close()
            End If
            Me._errMsg = ex.Message
            Me._errState = errState.IO_ERROR
            Me._src = Nothing
            Me._trg = Nothing
            Me._projType = ProjectType.SQL
            Me._outputLang = ProjectLang.VBNET11
            Return
        End Try
    End Sub

    Private Sub LoadXMLFile(ByVal fileName As String)
        Dim doc As New XmlDocument()
        doc.Load(fileName)

        Dim version As String = ""
        Try
            version = doc.SelectSingleNode(NodeName.Project.ToString & "/" & NodeName.Version.ToString()).InnerText
        Catch ex As Exception
            version = BBXProject.Versions.CurrentVersion()
        End Try

        Try
            Me._projType = [Enum].Parse(GetType(ProjectType), Me.ReadConfigValue(doc, ConfigName.ProjectType))
        Catch ex As Exception
            Me._projType = ProjectType.SQL
        End Try

        Try
            Me._outputLang = [Enum].Parse(GetType(ProjectLang), Me.ReadConfigValue(doc, ConfigName.OutputLang))
        Catch ex As Exception
            Me._outputLang = ProjectLang.VBNET11
        End Try

        Try
            Me._outputDObj = [Boolean].Parse(Me.ReadConfigValue(doc, ConfigName.OutputDataObject))
        Catch ex As Exception
            Me._outputDObj = False
        End Try

        Try
            Me._outputDCol = [Boolean].Parse(Me.ReadConfigValue(doc, ConfigName.OutputDOCollection))
        Catch ex As Exception
            Me._outputDCol = False
        End Try

        Try
            Me._outputTObj = [Boolean].Parse(Me.ReadConfigValue(doc, ConfigName.OutputDOTO))
        Catch ex As Exception
            Me._outputTObj = False
        End Try

        Try
            Me._enableRuntimeBoundsCheck = [Boolean].Parse(Me.ReadConfigValue(doc, ConfigName.EnableBoundsChecking))
        Catch ex As Exception
            Me._enableRuntimeBoundsCheck = False
        End Try

        Try
            Me._enableDTStyleDateMapping = [Boolean].Parse(Me.ReadConfigValue(doc, ConfigName.EnableDTDateMapping))
        Catch ex As Exception
            Me._enableDTStyleDateMapping = False
        End Try

        Me._src = Me.ReadConfigValue(doc, ConfigName.Source)
        Me._trg = Me.ReadConfigValue(doc, ConfigName.Target)

        Me.ReadMapping(doc)

    End Sub


    Private Function ReadConfigValue(ByVal doc As XmlDocument, ByVal configName As ConfigName) As String
        Dim node As XmlNode = doc.SelectSingleNode(NodeName.Project.ToString & "/" & NodeName.Configuration.ToString & "/" & configName.ToString)
        If Not node Is Nothing Then
            Return node.InnerText
        Else
            Return ""
        End If
    End Function

    Private Sub ReadMapping(ByVal doc As XmlDocument)
        Me._mappedTables.Clear()
        Dim nodes As XmlNodeList = doc.SelectNodes(NodeName.Project.ToString & "/" & NodeName.Mapping.ToString & "/" & NodeName.Table.ToString)
        For i As Integer = 0 To nodes.Count - 1
            Dim node As XmlNode = nodes(i)
            Dim tableName As String = node.Attributes("Name").Value
            Dim mapTable As New MappedTable
            mapTable.ChildTableName = tableName
            Try
                mapTable.ParentTableName = node.SelectSingleNode(NodeName.InheritsFrom.ToString & "/" & "ParentTable").InnerText
                mapTable.ParentColumnName = node.SelectSingleNode(NodeName.InheritsFrom.ToString & "/" & "ParentColumn").InnerText
                mapTable.ChildColumnName = node.SelectSingleNode(NodeName.InheritsFrom.ToString & "/" & "ChildColumn").InnerText
            Catch ex As Exception
            End Try

            Dim nodeConstructor As XmlNode = node.SelectSingleNode(NodeName.GenerateDefaultConstructor.ToString)
            If Not nodeConstructor Is Nothing Then
                mapTable.GenerateDefaultConstructor = nodeConstructor.InnerText
            End If

            Me._mappedTables.Add(tableName, mapTable)
        Next
    End Sub

#Region "Legacy Loading Code"

    Private Class Command
        Public myCommand As ConfigName
        Public action As String
    End Class

    Public Sub LoadTextFile(ByVal fileName As String)
        Dim sr As StreamReader = Nothing
        Try
            Dim s As Stream = File.OpenRead(fileName)
            sr = New StreamReader(s)

            Dim lines As New ArrayList
            Dim done As Boolean = False

            While Not done
                Dim line As String = sr.ReadLine()
                If line Is Nothing Then
                    done = True
                Else
                    lines.Add(line)
                End If
            End While
            sr.Close()

            Me._errState = errState.OK
            Me._errMsg = Nothing
            For i As Integer = 1 To lines.Count - 1
                Dim cmd As Command = ParseCommand(lines(i))
                Select Case cmd.myCommand
                    Case ConfigName.OutputLang
                        Me._outputLang = [Enum].Parse(GetType(ProjectLang), cmd.action)
                    Case ConfigName.OutputDataObject
                        Me._outputDObj = [Boolean].Parse(cmd.action)
                    Case ConfigName.OutputDOCollection
                        Me._outputDCol = [Boolean].Parse(cmd.action)
                    Case ConfigName.OutputDOTO
                        Me._outputTObj = [Boolean].Parse(cmd.action)
                    Case ConfigName.ProjectType
                        Me._projType = [Enum].Parse(GetType(ProjectType), cmd.action)
                    Case ConfigName.Source
                        Me._src = cmd.action
                    Case ConfigName.Target
                        Me._trg = cmd.action
                    Case ConfigName.EnableBoundsChecking
                        Me.EnableRuntimeBoundsCheck = [Boolean].Parse(cmd.action)
                    Case ConfigName.EnableDTDateMapping
                        Me.EnableDTStyleDateMapping = [Boolean].Parse(cmd.action)
                End Select
            Next
            Return
        Catch ex As Exception
            If Not sr Is Nothing Then
                sr.Close()
            End If
        End Try
    End Sub

    Private Function ParseCommand(ByVal input As String) As Command
        Dim idx As Integer = input.IndexOf("=")

        If idx < 0 Then Return Nothing
        If idx + 1 > input.Length Then Return Nothing

        Dim cmdStr As String = input.Substring(0, idx)
        Dim cmd As New Command
        Try
            cmd.myCommand = [Enum].Parse(GetType(ConfigName), cmdStr)
        Catch ex As Exception
            Return Nothing
        End Try
        cmd.action = input.Substring(idx + 1)
        Return cmd
    End Function

#End Region



#End Region


#Region "Saving"

    Public Sub Save(ByVal streamHnd As Stream)
        Try
            Me._errState = errState.OK
            Me._errMsg = Nothing

            Dim doc As New XmlDocument()
            Dim root As XmlElement = doc.CreateElement(NodeName.Project.ToString)
            doc.AppendChild(root)

            Dim versionNode As XmlElement = doc.CreateElement(NodeName.Version.ToString)
            versionNode.InnerText = BBXProject.Versions.CurrentVersion
            root.AppendChild(versionNode)

            Dim configParentNode As XmlElement = doc.CreateElement(NodeName.Configuration.ToString)
            root.AppendChild(configParentNode)

            Me.AddConfigValue(doc, configParentNode, ConfigName.Source, Me.Source)
            Me.AddConfigValue(doc, configParentNode, ConfigName.Target, Me.Destination)
            Me.AddConfigValue(doc, configParentNode, ConfigName.ProjectType, Me.CurrentProject.ToString)
            Me.AddConfigValue(doc, configParentNode, ConfigName.OutputLang, Me.OutputLanguage.ToString)
            Me.AddConfigValue(doc, configParentNode, ConfigName.OutputDataObject, Me.OutputDataObject.ToString)
            Me.AddConfigValue(doc, configParentNode, ConfigName.OutputDOCollection, Me.OutputDOCollection.ToString)
            Me.AddConfigValue(doc, configParentNode, ConfigName.OutputDOTO, Me.OutputDOTO.ToString)
            Me.AddConfigValue(doc, configParentNode, ConfigName.EnableBoundsChecking, Me.EnableRuntimeBoundsCheck.ToString)
            Me.AddConfigValue(doc, configParentNode, ConfigName.EnableDTDateMapping, Me.EnableDTStyleDateMapping.ToString)


            'Add the mapping data
            Dim mappingParentNode As XmlElement = doc.CreateElement(NodeName.Mapping.ToString)
            root.AppendChild(mappingParentNode)
            For Each key As String In Me.MappedTables.Keys
                Dim map As MappedTable = Me.MappedTables(key)
                Dim tableNode As XmlElement = doc.CreateElement(NodeName.Table.ToString)
                Dim attr As XmlAttribute = doc.CreateAttribute("Name")
                attr.Value = map.ChildTableName
                tableNode.Attributes.Append(attr)
                mappingParentNode.AppendChild(tableNode)
                Dim inheritsNode As XmlElement = doc.CreateElement(NodeName.InheritsFrom.ToString)
                tableNode.AppendChild(inheritsNode)
                Dim node As XmlElement
                node = doc.CreateElement(NodeName.ParentTable.ToString)
                node.InnerText = map.ParentTableName
                inheritsNode.AppendChild(node)
                node = doc.CreateElement(NodeName.ParentColumn.ToString)
                node.InnerText = map.ParentColumnName
                inheritsNode.AppendChild(node)
                node = doc.CreateElement(NodeName.ChildColumn.ToString)
                node.InnerText = map.ChildColumnName
                inheritsNode.AppendChild(node)
            Next
            doc.Save(streamHnd)
        Catch ex As Exception
            Me._errMsg = ex.Message
            Me._errState = errState.IO_ERROR
            Return
        Finally
            streamHnd.Close()
        End Try
    End Sub


    Private Sub AddConfigValue(ByVal doc As XmlDocument, ByVal configParentNode As XmlElement, ByVal name As ConfigName, ByVal innerText As String)
        Dim node As XmlElement = doc.CreateElement(name.ToString)
        node.InnerText = innerText
        configParentNode.AppendChild(node)
    End Sub

#End Region


#Region "Parsing and Rendering"

    Private tables As TableList = Nothing

    Public Sub Import()
        Dim fileParserType As Type = Nothing
        Select Case Me.CurrentProject
            Case ProjectType.SQL
                fileParserType = GetType(SQL)
            Case ProjectType.SCRIPT
                fileParserType = GetType(Script)
        End Select
        Dim src As String = ReadFromDisk(Me.Source)
        Dim obj As AbstractFileParser = CType(fileParserType.InvokeMember(Nothing, BindingFlags.DeclaredOnly Or BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.CreateInstance, Nothing, Nothing, New Object() {}), AbstractFileParser)
        Me.tables = obj.Parse(src, Me)
        Me.UpdateMappedTables(tables)
    End Sub

    Private Function ReadFromDisk(ByVal filename As String) As String
        'Try
        Dim src As StreamReader
        Dim inData As String
        src = File.OpenText(filename)
        inData = src.ReadToEnd()
        src.Close()
        Return inData.Trim()
        'Catch ex As Exception
        '    MsgBox("Fatal error reading from disk." & vbCrLf & ex.Message, MsgBoxStyle.Critical Or MsgBoxStyle.OKOnly, "BizBlox Error")
        '    End
        'End Try
    End Function

    Private Sub UpdateMappedTables(ByVal tables As TableList)
        For i As Integer = 0 To tables.Count - 1
            Dim childTable As Table = tables(i)
            If Me.MappedTables.Contains(childTable.TableName) Then
                Dim map As MappedTable = MappedTables(childTable.TableName)
                Dim parentTableName As String = map.ParentTableName
                If parentTableName <> "" Then
                    Dim parentTable As Table = tables(parentTableName)
                    For j As Integer = 0 To parentTable.Columns.Count - 1
                        Dim col As Column = parentTable.Columns(j)
                        If Not col.ColumnName = map.ParentColumnName Then
                            childTable.Columns.Add(col)
                        End If
                    Next
                End If
                childTable.GenerateDefaultConstructor = map.GenerateDefaultConstructor
            End If
        Next
    End Sub

    Public Sub performRender(ByVal callback As pixolut.BizBloxBuilderCore.AbstractRenderEngine.UpdateProgress, ByVal detail As pixolut.BizBloxBuilderCore.AbstractRenderEngine.UpdateStatus)
        If Me.tables Is Nothing Then
            Me.Import()
        End If
        Dim re As AbstractRenderEngine = Nothing
        If Me.CurrentProject = BBXProject.ProjectType.SQL Then
            If Me.OutputDataObject Then
                'Note: right now 1.1 is the same as 2.0 so we use the same classes.
                Select Case Me.OutputLanguage
                    Case BBXProject.ProjectLang.CSNET11
                        re = New CSDORenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.VBNET11
                        re = New VBDORenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.CSNET20
                        re = New CSDORenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.VBNET20
                        re = New VBDORenderEngine(callback, detail)
                End Select
                re.Project = Me
                re.OutputClasses(Me.Destination, Me.tables)
            End If

            If Me.OutputDOCollection Then
                Select Case Me.OutputLanguage
                    Case BBXProject.ProjectLang.CSNET11
                        re = New CSDOCollectionRenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.VBNET11
                        re = New VBDOCollectionRenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.CSNET20
                        re = New CSDOCollectionRenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.VBNET20
                        re = New VBDOCollectionRenderEngine(callback, detail)
                End Select
                re.Project = Me
                re.OutputClasses(Me.Destination, Me.tables)
            End If
            If Me.OutputDOTO Then
                Select Case Me.OutputLanguage
                    Case BBXProject.ProjectLang.CSNET11
                        re = New CSTORenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.VBNET11
                        re = New VBTORenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.CSNET20
                        re = New CSTORenderEngine(callback, detail)
                    Case BBXProject.ProjectLang.VBNET20
                        re = New VBTORenderEngine(callback, detail)
                End Select
                re.Project = Me
                re.OutputClasses(Me.Destination, Me.tables)
            End If
        Else
            Select Case Me.OutputLanguage
                Case BBXProject.ProjectLang.CSNET11
                    re = New CSTORenderEngine(callback, detail)
                Case BBXProject.ProjectLang.VBNET11
                    re = New VBTORenderEngine(callback, detail)
                Case BBXProject.ProjectLang.CSNET20
                    re = New CSTORenderEngine(callback, detail)
                Case BBXProject.ProjectLang.VBNET20
                    re = New VBTORenderEngine(callback, detail)
            End Select
            re.Project = Me
            re.OutputClasses(Me.Destination, Me.tables)
        End If
    End Sub


#End Region



End Class
